using System;
using System.IO;
using System.Text;
using System.Globalization;

namespace Org.JSON
{
	public abstract class Value
	{
		internal Value()
		{

		}

		public string Serialize()
		{
			var buffer = new StringWriter();
			this.Serialize(buffer);
			return buffer.ToString();
		}

		public abstract void Serialize(TextWriter stream);

		internal virtual bool DoesRecursion(Value value)
		{
			return value == this;
		}

		internal void SafeAddCheck(Value value)
		{
			if(value == null)
			{
				throw new ArgumentNullException();
			}

			if(value.DoesRecursion(this))
			{
				throw new OverflowException();
			}
		}

		public static Value From(string json)
		{
			return From(new StringReader(json));
		}


		public static Value From(TextReader stream)
		{
			var rv = Parse(stream);
			if(stream.Read() != -1 )
			{
				throw new InvalidDataException("Expected end of json");
			}
			return rv;
		}

		private static Value Parse(TextReader stream)
		{
			var c =(char)stream.Peek();

			//Aca tendria que hacer un null
			if(c == 'n') {
				stream.RequireString("null");
				return new Org.JSON.Null();
			}
			else if(c == 't')
			{
				stream.RequireString("true");
				return new Org.JSON.Boolean(true);
			}
			else if(c == 'f')
			{
				stream.RequireString("false");
				return new Org.JSON.Boolean(false);
			}
			else if(c == '[')
			{
				stream.Read();
				var first = true;
				var array = new Org.JSON.Array();
				while(((char)stream.Peek()) != ']')
				{
					if(first)
					{
						first = false;
					}
					else
					{
						stream.RequireString(",");
					}

					array.Add(Parse(stream));
				}
				stream.Read();
				return array;
			}
			else if(c == '{')
			{
				stream.Read();
				var first = true;
				var obj = new Org.JSON.Object();
				while(((char)stream.Peek()) != '}')
				{
					if(first)
					{
						first = false;
					}
					else
					{
						stream.RequireString(",");
					}
				}
				stream.Read();
				return obj;
			}
			else if(c == '"')
			{
				//Omito la primer comilla
				stream.Read();

				var buffer = new StringBuilder();

				while(true) {
					c = (char) stream.Read();

					//Termino al final de la cadena
					if(c == '"')
						break;

					//Hay un espace
					if(c == '\\')
					{
						c = (char) stream.Read();

						if(c == '"')
							buffer.Append('"');
						else if(c == '\\')
							buffer.Append('\\');
						else if(c == '/')
							buffer.Append('/');
						else if(c == 'n')
							buffer.Append('\n');
						else if(c == 'r')
							buffer.Append('\r');
						else if(c == 'f')
							buffer.Append('\f');
						else if(c == 'b')
							buffer.Append('\b');
						else if(c == 't')
							buffer.Append('\t');
						else if(c == 'u')
						{
							buffer.Append((char)Convert.ToInt32(stream.ReadChar().ToString() + stream.ReadChar()
								+stream.ReadChar() + stream.ReadChar(),16));
						}
						else
							throw new InvalidDataException("Invalid escape sequence");

					}
					else if(c < 0x20 || c >= 0x80 && c <= 0x9F)
					{
						throw new InvalidDataException("Invalid string character");
					}
					//TODO: Comprobar que no halla un caracter verdura
					else
					{
						buffer.Append(c);
					}
				}

				return new Org.JSON.String(buffer.ToString());

			}

			else {
				throw new NotImplementedException();
			}
		}
	}

	internal static class TextReaderHelper
	{
		public static void RequireString(this TextReader stream, string s)
		{
			foreach(var c in s)
				if(((char)stream.Read() != c))
					throw new InvalidDataException();
		}

		public static char ReadChar(this TextReader stream)
		{
			return (char) stream.Read();
		}
	}
}

